﻿using System;
using System.Collections.Generic;
using System.Linq;
using JustAgile.Html.Linq;
using UniUni.Text;

namespace HtmlInputExtractor.Common {
	public static class HtmlInputExtractor {
		private static readonly Dictionary<string, ComponentType> TypeValue2InputType;
		private static readonly Dictionary<string, ComponentType> TypeName2InputType;

		static HtmlInputExtractor() {
			TypeValue2InputType = new Dictionary<string, ComponentType>();
			TypeValue2InputType["text"] = ComponentType.Text;
			TypeValue2InputType["password"] = ComponentType.Password;
			TypeValue2InputType["file"] = ComponentType.File;
			TypeValue2InputType["checkbox"] = ComponentType.CheckBox;
			TypeValue2InputType["radio"] = ComponentType.RadioButton;
			TypeValue2InputType["hidden"] = ComponentType.Hidden;
			TypeValue2InputType["submit"] = ComponentType.Submit;
			TypeValue2InputType["reset"] = ComponentType.Reset;
			TypeValue2InputType["button"] = ComponentType.Button;
			TypeValue2InputType["image"] = ComponentType.ImageButton;
			TypeName2InputType = new Dictionary<string, ComponentType>();
			TypeName2InputType["ラベル"] = ComponentType.Label;
			TypeName2InputType["リンク"] = ComponentType.Link;
			TypeName2InputType["テキストボックス"] = ComponentType.Text;
			TypeName2InputType["パスワード"] = ComponentType.Password;
			TypeName2InputType["ファイル選択"] = ComponentType.File;
			TypeName2InputType["チェックボックス"] = ComponentType.CheckBox;
			TypeName2InputType["ラジオボタン"] = ComponentType.RadioButton;
			TypeName2InputType["Hidden"] = ComponentType.Hidden;
			TypeName2InputType["送信ボタン"] = ComponentType.Submit;
			TypeName2InputType["リセットボタン"] = ComponentType.Reset;
			TypeName2InputType["ボタン"] = ComponentType.Button;
			TypeName2InputType["イメージボタン"] = ComponentType.ImageButton;
		}

		private static IDictionary<string, string> ParseComment(HNode element) {
			var result = new Dictionary<string, string>();
			var comments = element.NodesBeforeSelf()
					.Where(e => !(e is HText))
					.Reverse()
					.TakeWhile(e => e is HComment);
			foreach (HComment comment in comments) {
				foreach (var item in comment.Value.Split(',')) {
					var array = item.Split('=');
					result[array[0].Trim().ToLower()] = array.Length > 1 ? array[1].Trim() : "";
				}
			}
			return result;
		}

		private static string GetString(IDictionary<string, string> dict, string key) {
			string value;
			if (dict.TryGetValue(key.ToLower(), out value)) {
				return value;
			}
			return null;
		}

		private static int? GetInt(IDictionary<string, string> dict, string key) {
			string value;
			int result;
			if (dict.TryGetValue(key.ToLower(), out value)
			    && int.TryParse(value, out result)) {
				return result;
			}
			return null;
		}

		private static bool GetBool(
				IDictionary<string, string> dict, string key, bool init) {
			string value;
			return dict.TryGetValue(key.ToLower(), out value)
			       		? value.ToLower() != "no" : init;
		}

		public static IList<HtmlInput> ExtractFromCsv(string csvFilePath) {
			var ret = new List<HtmlInput>();
			var lines = GuessEncoding.ReadAllText(csvFilePath)
					.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries);
			foreach (var line in lines.Skip(1)) {
				var items = line.Split(',')
					.Select(s => string.IsNullOrWhiteSpace(s) ? null : s)
					.ToList();
				var i = 0;
				ret.Add(
						new HtmlInput {
								Name = items[++i],
								Type = TypeName2InputType[items[++i]],
								Value = items[++i],
								TabOrder = items[++i].ToIntOrDefault(),
								IsNumber = items[++i] == "●",
								IsAlphabet = items[++i] == "●",
								IsSign = items[++i] == "●",
								IsKana = items[++i] == "●",
								IsZenkaku = items[++i] == "●",
								IsDate = items[++i] == "●",
								IsOther = items[++i] == "●",
								IsRequired = items[++i] == "●",
								MinLength = items[++i].ToIntOrDefault(),
								MaxLength = items[++i].ToIntOrDefault(),
								MinValue = items[++i].ToIntOrDefault(),
								MaxValue = items[++i].ToIntOrDefault(),
								Alignment = items[++i],
								Format = items[++i],
								Transition = items[++i],
								TransitionMethod = items[++i],
								IsInput = items[++i] == "●",
								IsOutput = items[++i] == "●",
								IsDbInput = items[++i] == "●",
								IsDbOutput = items[++i] == "●",
								DbName = items[++i],
								Others = items[++i],
								Id = items[++i],
						});
			}
			return ret;
		}

		public static IEnumerable<HtmlInput> Extract(
				string htmlFilePath, IList<HtmlInput> htmlInputs) {
			var doc = HDocument.Load(htmlFilePath);
			return doc.Descendants()
					.Where(
							e =>
							e.Name.LocalName == "span" || e.Name.LocalName == "a" || e.Name.LocalName == "select" ||
							(e.Name.LocalName == "input"
							 && TypeValue2InputType.ContainsKey(e.Attribute("type").Value)))
					.Select(
							e => {
								var result = new HtmlInput();
								// Htmlコメントを解析する
								var dict = ParseComment(e);
								var name = GetString(dict, "名前");
								var id = GetString(dict, "ID");
								// jQuery Validationを解析する(HTMLコメントより優先度高)
								ParseJQueryValidation(e, dict);
								// 必要であればHtmlを解析してnameとid属性の値を得る
								if (name == null) {
									if (e.Name.LocalName == "input") {
										var nameAttr = e.Attribute("name");
										name = nameAttr != null ? nameAttr.Value : null;
									} else {
										name = e.Value;
									}
								}
								if (id == null) {
									var idAttr = e.Attribute("id");
									id = idAttr != null ? idAttr.Value : name;
								}

								// IDが同じ要素が存在するかチェック
								var input = htmlInputs.FirstOrDefault(hi => hi.Id == id);
								if (input != null) {
									result = input;
									htmlInputs.Remove(input);
								}

								var valueAttr = e.Attribute("value");
								var hrefAttr = e.Attribute("href");
								switch (e.Name.LocalName) {
								case "input":
									result.Value = valueAttr != null ? valueAttr.Value : null;
									result.Type = TypeValue2InputType[e.Attribute("type").Value];
									result.IsInput = true;
									result.IsOutput = true;
									break;
								case "select":
									result.Value = valueAttr != null ? valueAttr.Value : null;
									result.Type = ComponentType.Text;
									result.IsInput = true;
									result.IsOutput = true;
									break;
								case "span":
									result.Type = ComponentType.Label;
									result.IsOutput = true;
									break;
								case "a":
									result.Type = ComponentType.Link;
									result.Transition = hrefAttr != null ? hrefAttr.Value : null;
									result.IsOutput = true;
									break;
								}

								result.Name = name ?? result.Name;
								result.Value = GetString(dict, "初期値") ?? result.Value;
								result.TabOrder = GetInt(dict, "Tab順") ?? result.TabOrder;
								result.IsNumber = GetBool(dict, "数字", result.IsNumber);
								result.IsAlphabet = GetBool(dict, "英字", result.IsAlphabet);
								result.IsSign = GetBool(dict, "記号", result.IsSign);
								result.IsKana = GetBool(dict, "カナ", result.IsKana);
								result.IsZenkaku = GetBool(dict, "全角", result.IsZenkaku);
								result.IsDate = GetBool(dict, "日付", result.IsDate);
								result.IsOther = GetBool(dict, "その他", result.IsOther);
								result.IsRequired = GetBool(dict, "必須項目", result.IsRequired);
								result.IsInput = GetBool(dict, "入力", result.IsInput);
								result.IsOutput = GetBool(dict, "出力", result.IsOutput);
								result.MinLength = GetInt(dict, "最小文字数") ?? result.MinLength;
								result.MaxLength = GetInt(dict, "最大文字数") ?? result.MaxLength;
								result.MinValue = GetInt(dict, "最小値") ?? result.MinValue;
								result.MaxValue = GetInt(dict, "最大値") ?? result.MaxValue;
								result.Alignment = GetString(dict, "文字寄せ") ?? result.Alignment;
								result.Format = GetString(dict, "フォーマット") ?? result.Format;
								result.Transition = GetString(dict, "遷移先") ?? result.Transition;
								result.TransitionMethod = GetString(dict, "遷移方法") ?? result.TransitionMethod;
								result.IsDbInput = GetBool(dict, "DB入力", result.IsDbInput);
								result.IsDbOutput = GetBool(dict, "DB出力", result.IsDbOutput);
								result.DbName = GetString(dict, "DB項目名") ?? result.DbName;
								result.Others = GetString(dict, "備考") ?? result.Others;
								result.Id = id ?? result.Id;
								return result;
							});
		}

		private static void ParseJQueryValidation(
				HElement element, IDictionary<string, string> dict) {
			ParseAttribute(element, dict, "min", "最小値");
			ParseAttribute(element, dict, "max", "最大値");
			ParseAttribute(element, dict, "minlength", "最小文字数");
			ParseAttribute(element, dict, "maxlength", "最大文字数");
			var attr = element.Attribute("class");
			if (attr != null && !string.IsNullOrEmpty(attr.Value)) {
				var str = attr.Value;
				if (str.Contains("required"))
					dict["必須項目"] = "yes";
				if (str.Contains("number"))
					dict["数字"] = "yes";
				if (str.Contains("date"))
					dict["日付"] = "yes";
				var extra = str.Replace("required", "")
						.Replace("number", "")
						.Replace("date", "")
						.Replace("  ", " ")
						.Trim();
				if (!string.IsNullOrWhiteSpace(extra)) {
					dict["備考"] = extra;
				}
			}
		}

		private static void ParseAttribute(
				HElement element, IDictionary<string, string> dict, string attributeName,
				string key) {
			var attr = element.Attribute(attributeName);
			if (attr != null && !string.IsNullOrEmpty(attr.Value)) {
				dict[key] = attr.Value;
			}
		}
	}
}